﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Zhy.ModbusLib.Base;

namespace Zhy.ModbusLib
{
    public class ModbusASCII : ModbusSerial
    {
        #region 字段
        internal List<AsyncModel> asyncModels = new List<AsyncModel>();
        #endregion

        #region 初始化
        /// <summary>
        /// 1、构造函数的初始化
        /// 2、有实例了，开始与设备进行连接
        /// </summary>
        public ModbusASCII() :
            this("COM1")
        { }
        public ModbusASCII(string portName) : this(portName, 9600, Parity.None, 8, StopBits.One)
        { }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="portName">端口名称</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">校验位</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="stopBits">停止位</param>
        public ModbusASCII(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) : base()
        {
            this.PortName = portName; // 端口号
            this.BaudRate = baudRate; // 波特率
            this.Parity = parity; // 校验位
            this.DataBits = dataBits; // 数据位
            this.StopBits = stopBits; // 停止位
        }
        #endregion

        #region Modbus读取数据
        /// <summary>
        /// 读取 4个存储区域
        //  从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <returns></returns>
        public override byte[] Read(byte slave, Functions func, ushort start, ushort count)
        {
            // 要发送的报文
            List<byte> bytes =
                this.GetReadBytes(slave, (byte)func, start, count).ToList();
            // 添加校验位-添加加头和尾
            var ascii = CreateAdu(bytes);

            // 适用于03，04功能码
            // 如果请求10个寄存器，正常响应51个字节，
            // 异常情况   5（RTU）　　　01 83 01 XX　　4*2+3=11 bytes
            // 计算接收报文的长度
            // 计算实例：获取10个寄存器：（10*2（每个寄存器2个字节）+1个地址+1个功能码+1个字节数+1个校验位）*2）(每个字符2个ASCII)+1个头部+2个尾部=共51个
            int len = (count * 2 + 4) * 2 + 3;

            // 适用01、02
            // 如果请求10个线圈，正常响应15
            // 线圈计算实例：线圈是二进制-8个线圈一个字节，所以要除以8 - 其它同上
            if (func == Functions.RCoilStatus || func == Functions.RInputCoils)
                len = ((int)Math.Ceiling(count * 1.0 / 8) + 4) * 2 + 3;
            // 发送和接收报文数据方法
            byte[] resp = this.SendAndReceive(ascii.ToArray(), len);
            // 返回的报文进行校验
            return this.Check(resp, len); // 返回接收到的报文
        }

        /// <summary>
        /// 读取 4个存储区域
        //  从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        /// <returns></returns>
        public override void ReadAsync(byte slave, Functions func, ushort start, ushort count, int handler, Action<byte[], int, Exception> callback)
        {
            // 拼接要发送的报文
            List<byte> bytes =
                this.GetReadBytes(slave, (byte)func, start, count).ToList();

            var ascii = CreateAdu(bytes); // 添加校验码
            // 适用于03，04功能码
            // 如果请求10个寄存器，正常响应51个字节，
            // 异常情况   5（RTU）　　　01 83 01 XX　　4*2+3=11 bytes
            // 计算接收报文的长度
            // 计算实例：获取10个寄存器：（10*2（每个寄存器2个字节）+1个地址+1个功能码+1个字节数+1个校验位）*2）(每个字符2个ASCII)+1个头部+2个尾部=共51个
            int len = (count * 2 + 4) * 2 + 3;
            // 适用01、02
            // 如果请求10个线圈，正常响应15
            // 线圈计算实例：线圈是二进制-8个线圈一个字节，所以要除以8 - 其它同上
            if (func == Functions.RCoilStatus || func == Functions.RInputCoils)
                len = ((int)Math.Ceiling(count * 1.0 / 8) + 4) * 2 + 3; 

            this.asyncModels.Add(new AsyncModel
            {
                Handler = handler,
                ReqBytes = bytes.ToArray(),
                RespLen = len,
                Completed = callback
            });
        }
        #endregion

        #region Modbus ASCII 写入操作
        /// <summary>
        /// Modbus ASCII 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        public override void Write(byte slave, Functions func, ushort start, ushort count, byte[] datas)
        {
            // 拼接要写入的报文
            List<byte> bytes =
                this.GetWriteBytes(slave, (byte)func, start, count, datas).ToList();
            // 添加校验码
            var ascii = CreateAdu(bytes);
            // 发送和接收返回报文方法
            // 7(1个地址+1个功能码+2个写入地址+2个写入数量+1个校验位) *2 + 3(1个:头部+2个尾部)  =  17 固定返回17个字节
            byte[] resp = this.SendAndReceive(ascii.ToArray(), 17);
            // 返回的报文进行校验
            this.Check(resp, 17);
            // 如果写入失败会发生异常
        }

        /// <summary>
        /// Modbus RTU 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        public override void WriteAsync(byte slave, Functions func, ushort start, ushort count, byte[] datas, int handler, Action<byte[], int, Exception> callback)
        {
            // 拼接要写入的报文
            List<byte> bytes =
                 this.GetWriteBytes(slave, (byte)func, start, count, datas).ToList();

            var ascii = CreateAdu(bytes); // 拼接校验码

            this.asyncModels.Add(new AsyncModel
            {
                Handler = handler,
                ReqBytes = bytes.ToArray(),
                RespLen = 17,
                Completed = callback
            });
        }
        #endregion

        /// <summary>
        /// 生成要发送的报文
        /// </summary>
        /// <param name="bytes">要发送的报文</param>
        /// <returns></returns>
        private List<byte> CreateAdu(List<byte> bytes)
        {
            byte lrc = this.LRC(bytes.ToArray()); // 生成校验码
            bytes.Add(lrc); // 添加校验位，是否反转取决于设备的大小端

            // 转ASCII字符   加头和尾  将每个字节转换成16进制字符串
            string str = ":" + string.Join("", bytes.Select(b => b.ToString("X2")));
            List<byte> ascii = Encoding.ASCII.GetBytes(str).ToList(); // 将字符串转换成ASCII
            // 4、尾部添加固定格式
            ascii.Add(0x0D);
            ascii.Add(0x0A);

            return ascii;
        }

        /// <summary>
        /// 返回的报文进行校验
        /// </summary>
        /// <param name="resp">返回的报文</param>
        /// <param name="len">正常报文接收长度</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected override byte[] Check(byte[] resp, int len)
        {
            // resp.Length == 11 表示异常码的报文(1个地址+1个功能码+1个故障原因+1个校验码)*2(每个字符2个ASCII)+3(1个头部+2个尾部)=共11个。
            // resp.Length == len 表示正常返回的报文
            if (resp.Length == 11 || resp.Length == len)
            {
                // 检查报文是否完整 -
                // resp[0] != 0x3A(检查头部是否是:号)
                // resp[resp.Length - 2] != 0x0D ||resp[resp.Length - 1] != 0x0A(检查尾部是否是0D、0A尾部是固定的)
                if (resp[0] != 0x3A || // (0x3A-Ascii中表示:-检查头部是否是: 号)
                resp[resp.Length - 2] != 0x0D ||
                resp[resp.Length - 1] != 0x0A)
                    throw new Exception("响应报文数据不完整");

                // 去头和尾
                List<byte> ascii = resp.ToList().GetRange(1, resp.Length - 3);
                // 将ascii码转换成对应的16进制字符创字符串
                string str = Encoding.ASCII.GetString(ascii.ToArray());
                // 将16进制字符串转变成字节数组
                byte[] datas = Convert.FromHexString(str);

                // LRC校验
                List<byte> check = datas.ToList().GetRange(0, datas.Length - 1);
                // 添加重新生成的校验码 - 反转是大小端的原因
                check.Add(LRC(check.ToArray()));
                // 判断接收的报文格式是否正确
                if (!check.SequenceEqual(datas))
                {
                    throw new Exception("数据传输异常，校验码不匹配");
                }

                // 报文异常码检查
                if (datas[1] > 0x80) // 功能码>0x80的是异常报文-否则是正常报文
                {
                    // resp[2] 是异常码 - 获取异常码的故障原因
                    throw new Exception(ModbusBase.Errors[datas[2]]);// 异常码
                }

                // 截取实际的报文数据-数据字节
                return datas.ToList().GetRange(3, datas.Length - 4).ToArray(); 
            }
            else // 报文无法识别 - 通信报文响应异常
                throw new Exception("通信响应异常");
        }
    }
}
